var whereEnum = fluentQryBaseEnum.extend({
    name: "where",
    prepare: function(clause) {
        this.$where = baseEnum_getClause(clause);
    },
    next: function() {
        while (this.source.next()) {
            if (this.$where.call(this, this.source.current)) {
                this.current = this.source.current;
                return true;
            }
        }
        this.current = null;
        return false;
    }
});
jsoq_regEnumClass("Where", whereEnum);
jsoq_regEnumMethod("where", function(clause) { return new whereEnum(this, clause); });

var selectEnum = fluentQryBaseEnum.extend({
    name: "select",
    prepare: function(func) {
        this.$select = baseEnum_getClause(func, default_clauses_returnCurrent);
    },
    next: function() {
        var itemValue;
        while (this.source.next()) {
            if ((itemValue = this.$select.call(this, this.source.current)) !== null) {
                this.current = itemValue;
                return true;
            }
        }
        this.current = null;
        return false;
    }
});
jsoq_regEnumClass("Select", selectEnum);
jsoq_regEnumMethod("select", function(func) { return new selectEnum(this, func); });

var selectManyEnum = fluentQryBaseEnum.extend({
    name: "selectMany",
    prepare: function(clause) {
        this.$select = baseEnum_getClause(clause, default_clauses_returnCurrent);
    },
    reset: function() {
        this.base();
        this.$buffer = null;
    },
    next: function() {
        var buffer = this.$buffer;
        if (buffer) {
            if (buffer.isEnum && buffer.next()) {
                this.current = buffer.current;
                return true;
            }
            else if (jsoq.isArray(buffer) && buffer.length || buffer.isList && buffer.length()) {
                this.current = buffer.shift();
                return true;
            }
        }
        var itemValue = null;
        var newBuffer = null;
        while (this.source.next()) {
            itemValue = this.$select(this.source.current);
            if (itemValue !== null) {
                newBuffer = jsoq.isArray(itemValue) && itemValue.length || itemValue.isEnum
                ? itemValue : itemValue.toEnum
                ? itemValue.ToEnum() : null;
                if (newBuffer) {
                    this.$buffer = newBuffer;
                    return this.next();
                }
                this.$buffer = null;
                this.current = itemValue;
                return true;
            }
        }
        this.current = null;
        return false;
    }
});
jsoq_regEnumClass("SelectMany", selectManyEnum);
jsoq_regEnumMethod("selectMany", function(func) { return new selectManyEnum(this, func); });

var takeEnum = fluentQryBaseEnum.extend({
    name: "take",
    prepare: function(count) {
        this.$count = count;
    },
    reset: function() {
        this.base();
        this.$taked = 0;
    },
    next: function() {
        if (this.$taked < this.$count) {
            while (this.source.next()) {
                this.current = this.source.current;
                ++this.$taked;
                return true;
            }
        }
        this.current = null;
        return false;
    }
});
jsoq_regEnumClass("Take", takeEnum);
jsoq_regEnumMethod("take", function(count) { return new takeEnum(this, count); });

var takeWhileEnum = fluentQryBaseEnum.extend({
    name: "takeWhile",
    prepare: function(clause) {
        this.$clause = baseEnum_getClause(clause);
    },
    next: function() {
        while (this.source.next() && this.$clause(this.source.current)) {
            this.current = this.source.current;
            return true;
        }
        this.current = null;
        return false;
    }
});
jsoq_regEnumClass("TakeWhile", takeWhileEnum);
jsoq_regEnumMethod("takeWhile", function(clause) { return new takeWhileEnum(this, clause); });

var exceptEnum = fluentQryBaseEnum.extend({
    name: "except",
    prepare: function(second, equalityComparer) {
        this.$second = jsoq.getEnum(second);
        this.$comparer = baseEnum_getClause(equalityComparer, default_clauses_equalityComparer);
    },
    next: function() {
        var second = this.$second;
        while (this.source.next()) {
            second.reset();
            while (second.next() && !this.$comparer(this.source.current, second.current)) { }
            if (second.current === null) {
                this.current = this.source.current;
                return true;
            }
        }
        this.current = null;
        return false;
    }
});
jsoq_regEnumClass("Except", exceptEnum);
jsoq_regEnumMethod("except", function(second, equalityComparer) { return new exceptEnum(this, second, equalityComparer); });

var unionEnum = fluentQryBaseEnum.extend({
    name: "union",
    prepare: function(second, equalityComparer) {
        this.$second = jsoq.getEnum(second);
        this.$comparer = baseEnum_getClause(equalityComparer, default_clauses_equalityComparer);
    },
    reset: function() {
        this.base();
        this.$cache = [];
        this.$cacheSize = null;
    },
    next: function() {
        if (this.source.next()) {
            this.current = this.source.current;
            this.$cache.push(this.current);
            return true;
        }
        else {
            if (this.$cacheSize === null) {
                this.$second.reset();
                this.$cacheSize = this.$cache.length;
            }
            var isEq;
            while (this.$second.next()) {
                isEq = false;
                for (var i = 0; i < this.$cacheSize && !isEq; i++) {
                    isEq = this.$comparer(this.$second.current, this.$cache[i]);
                }
                if (!isEq) {
                    this.current = this.$second.current;
                    return true;
                }
            }
        }
        this.current = null;
        return false;
    }
});
jsoq_regEnumClass("Union", unionEnum);
jsoq_regEnumMethod("union", function(second, equalityComparer) { return new unionEnum(this, second, equalityComparer); });

var concatEnum = fluentQryBaseEnum.extend({
    name: "concat",
    prepare: function(second) {
        this.$second = jsoq.getEnum(second);
    },
    reset: function() {
        this.base();
        this.$onFirst = true;
    },
    next: function() {
        if (this.$onFirst && this.source.next()) {
            this.current = this.source.current;
            return true;
        }
        else {
            if (this.$onFirst) {
                this.$second.reset();
                this.$onFirst = false;
            }
            if (this.$second.next()) {
                this.current = this.$second.current;
                return true;
            }
        }
        this.current = null;
        return false;
    }
});
jsoq_regEnumClass("Concat", concatEnum);
jsoq_regEnumMethod("concat", function(second) { return new concatEnum(this, second); });

var distinctEnum = fluentQryBaseEnum.extend({
    name: "distinct",
    prepare: function(equalityComparer) {
        this.$comparer = baseEnum_getClause(equalityComparer, default_clauses_equalityComparer);
    },
    reset: function() {
        this.base();
        this.$cache = [];
    },
    next: function() {
        var isEq = false;
        while (this.source.next()) {
            isEq = false;
            for (var i = 0, cacheLen = this.$cache.length; i < cacheLen && !isEq; i++)
                isEq = this.$comparer(this.source.current, this.$cache[i]);
            this.$cache.push(this.source.current);
            if (!isEq) {
                this.current = this.source.current;
                return true;
            }
        }
        this.current = null;
        return false;
    }
});
jsoq_regEnumClass("Distinct", distinctEnum);
jsoq_regEnumMethod("distinct", function(equalityComparer) { return new distinctEnum(this, equalityComparer); });

var skipEnum = fluentQryBaseEnum.extend({
    name: "skip",
    prepare: function(count) {
        this.$count = count;
    },
    next: function() {
        if (this.$count > 0) { while (this.source.next() && --this.$count > 0) { } }
        if (this.source.next()) {
            this.current = this.source.current;
            return true;
        }
        this.current = null;
        return false;
    }
});
jsoq_regEnumClass("Skip", skipEnum);
jsoq_regEnumMethod("skip", function(count) { return new skipEnum(this, count); });

var skipWhileEnum = fluentQryBaseEnum.extend({
    name: "skipWhile",
    prepare: function(clause) {
        this.$clause = baseEnum_getClause(clause, default_clauses_returnCurrent);
    },
    reset: function() {
        this.base();
        this.$skipped = false;
    },
    next: function() {
        if (!this.$skipped) {
            while (this.source.next() && this.$clause(this.source.current)) { }
            this.$skipped = true;
        }
        if (this.source.next()) {
            this.current = this.source.current;
            return true;
        }
        this.current = null;
        return false;
    }
});
jsoq_regEnumClass("SkipWhile", skipWhileEnum);
jsoq_regEnumMethod("skipWhile", function(clause) { return new skipWhileEnum(this, clause); });

var groupByEnum = fluentQryBaseEnum.extend({
    name: "groupBy",
    prepare: function(clause) {
        this.$clause = baseEnum_getClause(clause, default_clauses_returnCurrent);
    },
    reset: function() {
        this.base();
        this.$pending = true;
        this.$groups = [];
    },
    next: function() {
        if (this.$pending) {
            this.$pending = false;
            var groups = {};
            var key;
            while (this.source.next()) {
                key = this.$clause(this.source.current);
                if (key !== null) {
                    if (groups[key] !== undefined)
                        this.$groups[groups[key]].items.push(this.source.current);
                    else
                        groups[key] = this.$groups.push({ key: key, items: [this.source.current] }) - 1;
                }
            }
        }
        if (this.$groups.length) {
            this.current = this.$groups.shift();
            return true;
        }
        this.current = null;
        return false;
    }
});
jsoq_regEnumClass("GroupBy", groupByEnum);
jsoq_regEnumMethod("groupBy", function(keyFunc) { return new groupByEnum(this, keyFunc); });

var joinEnum = fluentQryBaseEnum.extend({
    name: 'join',
    prepare: function(second, matchClause, resultClause) {
        this.$second = jsoq.getEnum(second);
        this.$match = baseEnum_getClause(matchClause, default_clauses_equalityComparer);
        this.$result = baseEnum_getClause(resultClause, function(x, y) { return [x, y]; });
    },
    reset: function() {
        this.base();
        this.$lastMatch = false;
    },
    next: function() {
        while (true) {
            if (!this.$lastMatch) {
                if (!this.source.next()) {
                    this.current = null;
                    return false;
                }
                this.$second.reset();
            }
            while (this.$second.next()) {
                if (this.$match(this.source.current, this.$second.current)) {
                    this.current = this.$result(this.source.current, this.$second.current);
                    this.$lastMatch = true;
                    return true;
                }
            }
            this.$lastMatch = false;
        }
    }
});
jsoq_regEnumClass("Join", joinEnum);
jsoq_regEnumMethod("join", function(second, matchClause, resultClause) { return new joinEnum(this, second, matchClause, resultClause); });